package org.duiduo.common.tool.util;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.symmetric.DES;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Hex;
import org.apache.poi.util.IOUtils;

import javax.crypto.*;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static java.nio.charset.StandardCharsets.US_ASCII;

/**
 * 加密工具类
 * AES 对称加密
 * RSA 非对称加密
 *
 * @author liangze
 * @date 2024/11/13 11:29
 */
@Slf4j
public class EncryptionUtil {

    private static final String aes_mysql_key = "kYMCMF4b3YHw";
    private static final String aes_key = "+kYMCMF4b+hW+3YiEPOFHw==";
    private static final String base64_key = "xiaoheiccsqr";
    private static final String des_key = "1RwHuTteQ5s=";
    private static final String des_2_key = "1234567890ABCDEF";

    /**
     * RSA生成公钥私钥
     *
     * @return 密钥对
     */
    public static Pair<String, String> rsaGenerate() {
        KeyPair pair = SecureUtil.generateKeyPair("RSA");
        return Pair.of(Base64.encode(pair.getPublic().getEncoded()), Base64.encode(pair.getPrivate().getEncoded()));
    }

    /**
     * RSA公钥加密
     *
     * @param publicKey 公钥
     * @param data      数据
     * @return 加密数据
     */
    public static String rsaEncryption(String publicKey, String data) {
        if (StrUtil.isBlank(publicKey)) {
            throw new IllegalArgumentException("RSA需要传入公钥进行加密");
        }
        RSA rsa = new RSA(null, publicKey);
        byte[] encrypt = rsa.encrypt(StrUtil.bytes(data, CharsetUtil.CHARSET_UTF_8), KeyType.PublicKey);
        return Base64.encode(encrypt);
    }

    /**
     * RSA私钥解密
     *
     * @param privateKey 私钥
     * @param data       加密数据
     * @return 数据
     */
    public static String rsaDecryption(String privateKey, String data) {
        if (StrUtil.isBlank(privateKey)) {
            throw new IllegalArgumentException("RSA需要传入私钥进行加密");
        }
        RSA rsa = new RSA(privateKey, null);
        byte[] decrypt = rsa.decrypt(Base64.decode(data), KeyType.PrivateKey);
        return new String(decrypt);
    }

    /**
     * AES生成key
     *
     * @return 密钥
     */
    public static String aesGenerate() {
        return Base64.encode(SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue()).getEncoded());
    }

    /**
     * AES加密
     *
     * @param data 数据
     * @return 加密数据
     */
    public static String aesEncryption(String data) {
        return aesEncryption(aes_key, data);
    }

    /**
     * AES解密
     *
     * @param data 加密数据
     * @return 数据
     */
    public static String aesDecryption(String data) {
        return aesDecryption(aes_key, data);
    }

    /**
     * AES加密
     *
     * @param key 密钥
     * @param data 数据
     * @return 加密数据
     */
    public static String aesEncryption(String key, String data) {
        if (StrUtil.isBlank(key)) {
            throw new IllegalArgumentException("AES需要传入密钥信息");
        }
        // aes算法的密钥要求是16位、24位、32位
        int[] array = {16, 24, 32};
        if (!ArrayUtil.contains(array, key.length())) {
            throw new IllegalArgumentException("AES密钥长度要求为16位、24位、32位");
        }
        SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, Base64.decode(key));
        byte[] encrypt = aes.encrypt(data);
        return Base64.encode(encrypt);
    }

    /**
     * AES解密
     *
     * @param key 密钥
     * @param data 加密数据
     * @return 数据
     */
    public static String aesDecryption(String key, String data) {
        if (StrUtil.isBlank(key)) {
            throw new IllegalArgumentException("AES需要传入密钥信息");
        }
        // aes算法的密钥要求是16位、24位、32位
        int[] array = {16, 24, 32};
        if (!ArrayUtil.contains(array, key.length())) {
            throw new IllegalArgumentException("AES密钥长度要求为16位、24位、32位");
        }
        SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, Base64.decode(key));
        byte[] decrypt = aes.decrypt(data);
        return new String(decrypt);
    }

    /**
     * MYSQL-AES加密
     *
     * @param data 数据
     * @return 加密数据
     */
    public static String aesMysqlEncryption(String data) {
        return aesMysqlEncryption(aes_mysql_key, data);
    }

    /**
     * MYSQL-AES解密
     *
     * @param data 加密数据
     * @return 数据
     */
    public static String aesMysqlDecryption(String data) {
        return aesMysqlDecryption(aes_mysql_key, data);
    }

    /**
     * MYSQL-AES加密
     *
     * @param encodeKey 密钥
     * @param data 数据
     * @return 加密数据
     */
    public static String aesMysqlEncryption(String encodeKey, String data) {
        if (StrUtil.isBlank(encodeKey)) {
            throw new IllegalArgumentException("AES需要传入密钥信息");
        }
        try {
            SecretKeySpec key = generateMySQLAESKey(encodeKey, "ASCII");
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(1, key);
            byte[] cleartext = data.getBytes(StandardCharsets.UTF_8);
            byte[] ciphertextBytes = cipher.doFinal(cleartext);
            return new String(Hex.encodeHex(ciphertextBytes)).toUpperCase();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * MYSQL-AES解密
     *
     * @param encodeKey 密钥
     * @param data 加密数据
     * @return 数据
     */
    public static String aesMysqlDecryption(String encodeKey, String data) {
        if (StrUtil.isBlank(encodeKey)) {
            throw new IllegalArgumentException("AES需要传入密钥信息");
        }
        try {
            SecretKey key = generateMySQLAESKey(encodeKey, "ASCII");
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(2, key);
            byte[] cleartext = Hex.decodeHex(data.toCharArray());
            byte[] ciphertextBytes = cipher.doFinal(cleartext);
            return new String(ciphertextBytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static SecretKeySpec generateMySQLAESKey(String key, String encoding) {
        try {
            byte[] finalKey = new byte[16];
            int i = 0;
            for (byte b : key.getBytes(encoding)) {
                int var10001 = i++;
                finalKey[var10001 % 16] ^= b;
            }
            return new SecretKeySpec(finalKey, "AES");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Base64加密
     *
     * @param data 待加密数据
     * @return 加密后字符串
     */
    public static String base64Encrypt(String data) {
        return Base64.encode(data, StandardCharsets.UTF_8);
    }

    /**
     * Base64解密
     *
     * @param data 待解密数据
     * @return 解密后字符串
     */
    public static String base64Decrypt(String data) {
        return Base64.decodeStr(data, StandardCharsets.UTF_8);
    }

    /**
     * md5加密
     *
     * @param data 待加密数据
     * @return 加密后字符串, 采用Hex编码
     */
    public static String md5Encrypt(String data) {
        return SecureUtil.md5(data);
    }

    /**
     * sha256加密
     *
     * @param data 待加密数据
     * @return 加密后字符串, 采用Hex编码
     */
    public static String sha256Encrypt(String data) {
        return SecureUtil.sha256(data);
    }

    /**
     * sm3加密
     *
     * @param data 待加密数据
     * @return 加密后字符串, 采用Hex编码
     */
    public static String sm3Encrypt(String data) {
        return SmUtil.sm3(data);
    }

    /**
     * 生成des密钥
     *
     * @return 密钥
     */
    public static String desGenerate() {
        return Base64.encode(SecureUtil.generateKey(SymmetricAlgorithm.DES.getValue()).getEncoded());
    }

    /**
     * des加密
     *
     * @param data 待加密数据
     * @return 加密数据
     */
    public static String desEncrypt(String data) {
        return desEncrypt(des_key, data);
    }

    /**
     * des加密
     *
     * @param secretKey 密钥
     * @param data 待加密数据
     * @return 加密数据
     */
    public static String desEncrypt(String secretKey, String data) {
        if (StrUtil.isBlank(secretKey)) {
            throw new IllegalArgumentException("DES需要传入密钥信息");
        }
        byte[] key = Base64.decode(secretKey);
        DES des = SecureUtil.des(key);
        return des.encryptHex(data);
    }

    /**
     * des解密
     *
     * @param data 加密数据
     * @return 数据
     */
    public static String desDecode(String data) {
        return desDecode(des_key, data);
    }

    /**
     * des解密
     *
     * @param secretKey 密钥
     * @param data 加密数据
     * @return 数据
     */
    public static String desDecode(String secretKey, String data) {
        if (StrUtil.isBlank(secretKey)) {
            throw new IllegalArgumentException("DES需要传入密钥信息");
        }
        byte[] key = Base64.decode(secretKey);
        DES des = SecureUtil.des(key);
        return des.decryptStr(data);
    }

    /**
     * des加密-2
     *
     * @param data 待加密数据
     * @return 加密数据
     */
    public static String desEncode2(String data) {
        return desEncode2(des_2_key, data);
    }

    /**
     * des加密-2
     *
     * @param key 密钥
     * @param data 待加密数据
     * @return 加密数据
     */
    public static String desEncode2(String key, String data) {
        if (StrUtil.isBlank(key)) {
            throw new IllegalArgumentException("DES需要传入密钥信息");
        }
        try {
            byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
            int num = 8 - dataBytes.length % 8;
            byte[] encData = Arrays.copyOf(dataBytes, dataBytes.length + num);
            for (int i = dataBytes.length; i < encData.length; i++) {
                encData[i] = Integer.valueOf(0).byteValue();
            }
            byte[] bytes = str2Bcd(key);
            DESKeySpec desKey = new DESKeySpec(bytes);
            // 创建一个密匙工厂，然后用它把DESKeySpec转换成
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
            SecretKey securekey = keyFactory.generateSecret(desKey);
            // Cipher对象实际完成加密操作
            Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding");
            // 用密匙初始化Cipher对象
            cipher.init(Cipher.ENCRYPT_MODE, securekey);
            // 现在，获取数据并加密
            // 正式执行加密操作
            return Base64.encode(cipher.doFinal(encData));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * des解密-2
     *
     * @param data 加密数据
     * @return 数据
     */
    public static String desDecode2(String data) {
        return desDecode2(des_2_key, data);
    }

    /**
     * des解密-2
     *
     * @param key 密钥
     * @param data 加密数据
     * @return 数据
     */
    public static String desDecode2(String key, String data) {
        if (StrUtil.isBlank(key)) {
            throw new IllegalArgumentException("DES需要传入密钥信息");
        }
        try {
            byte[] bytes = str2Bcd(key);
            // 创建一个DESKeySpec对象
            DESKeySpec desKey = new DESKeySpec(bytes);
            // 创建一个密匙工厂
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
            // 将DESKeySpec对象转换成SecretKey对象
            SecretKey securekey = keyFactory.generateSecret(desKey);
            // Cipher对象实际完成解密操作
            Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding");
            // 用密匙初始化Cipher对象
            cipher.init(Cipher.DECRYPT_MODE, securekey);
            // 真正开始解密操作
            byte[] result = cipher.doFinal(Base64.decode(data));
            return new String(result, StandardCharsets.UTF_8).trim();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 文件打包
     *
     * @param file
     * @param targetFile
     * @return
     */
    public static String doZip(String file, String targetFile) {
        Assert.isFalse(StrUtil.isNotBlank(file), "file is not null");
        Assert.isFalse(StrUtil.isNotBlank(targetFile), "targetFile is not null");
        try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(targetFile))) {
            zipOut.putNextEntry(new ZipEntry(FileUtil.getName(file)));
            try (InputStream in = new FileInputStream(file)) {
                byte[] buf = new byte[1024];
                int len;
                while ((len = in.read(buf)) != -1) {
                    zipOut.write(buf, 0, len);
                }
            }
            zipOut.closeEntry();
            zipOut.finish();
            zipOut.flush();
        } catch (Exception e) {
            throw new RuntimeException("文件打包失败:(", e);
        }
        log.info("文件打包ok:::");
        return targetFile;
    }

    /**
     * 文件打包
     *
     * @param fileList
     * @param targetFile
     * @return
     */
    public static String doZip(List<String> fileList, String targetFile) {
        Assert.isFalse(fileList != null && !fileList.isEmpty(), "fileList is not null");
        Assert.isFalse(StrUtil.isNotBlank(targetFile), "targetFile is not null");
        try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(targetFile))) {
            for (String file : fileList) {
                zipOut.putNextEntry(new ZipEntry(FileUtil.getName(file)));
                try (InputStream in = new FileInputStream(file)) {
                    byte[] buf = new byte[1024];
                    int len;
                    while ((len = in.read(buf)) != -1) {
                        zipOut.write(buf, 0, len);
                    }
                }
                zipOut.closeEntry();
            }
            zipOut.finish();
            zipOut.flush();
        } catch (Exception e) {
            throw new RuntimeException("文件打包失败:(", e);
        }
        log.info("文件打包ok:::");
        return targetFile;
    }

    /**
     * AES-128-CBC 文件密码加密
     * <p>注: 文件处理完成记得删除</p>
     *
     * <pre class="code">
     * Files.delete(Paths.get(outPath));
     * </pre>
     * @param password   加密密码
     * @param sourcePath 文件路径
     * @param outPath    输出路径
     */
    public static String aesFileEncryption(String password, String sourcePath, String outPath) {
        final String SALTED_STR = "Salted__";
        final byte[] SALTED_MAGIC = SALTED_STR.getBytes(US_ASCII);
        FileInputStream fis = null;
        FileOutputStream fos = null;
        CipherOutputStream cos = null;
        try {
            fis = new FileInputStream(sourcePath);
            fos = new FileOutputStream(outPath);
            final byte[] pass = password.getBytes(US_ASCII);
            final byte[] salt = (new SecureRandom()).generateSeed(8);
            fos.write(SALTED_MAGIC);
            fos.write(salt);

            final byte[] passAndSalt = array_concat(pass, salt);
            byte[] hash = new byte[0];
            byte[] keyAndIv = new byte[0];
            for (int i = 0; i < 3 && keyAndIv.length < 48; i++) {
                final byte[] hashData = array_concat(hash, passAndSalt);
                final MessageDigest md = MessageDigest.getInstance("MD5");
                hash = md.digest(hashData);
                keyAndIv = array_concat(keyAndIv, hash);
            }

            final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 16);
            final byte[] iv = Arrays.copyOfRange(keyAndIv, 16, 32);
            final SecretKeySpec key = new SecretKeySpec(keyValue, "AES");

            final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
            cos = new CipherOutputStream(fos, cipher);
            int b;
            byte[] d = new byte[8];
            while ((b = fis.read(d)) != -1) {
                cos.write(d, 0, b);
            }
            // cos.flush();
            // cos.close();
            // fis.close();
        } catch (IOException | NoSuchPaddingException |
                 InvalidKeyException | InvalidAlgorithmParameterException |
                 NoSuchAlgorithmException e) {
            throw new RuntimeException("文件加密失败:", e);
        } finally {
            IOUtils.closeQuietly(cos);
            IOUtils.closeQuietly(fis);
        }
        log.info("文件加密ok:::");
        return outPath;
    }

    public static byte[] array_concat(final byte[] a, final byte[] b) {
        final byte[] c = new byte[a.length + b.length];
        System.arraycopy(a, 0, c, 0, a.length);
        System.arraycopy(b, 0, c, a.length, b.length);
        return c;
    }

    public static byte[] str2Bcd(String asc) {
        try {
            asc = asc.trim();
            int len = asc.length();
            int mod = len % 2;
            if (mod != 0) {
                asc = "0" + asc;
                len = asc.length();
            }
            byte[] ret = new byte[len / 2];
            for (int i = 0; i < ret.length; i++) {
                ret[i] = (Integer.valueOf(asc.substring(i + i, (i + i + 2)), 16)).byteValue();
            }
            return ret;
        } catch (Exception e) {
            throw new RuntimeException("字符串转换成BCD码错误:", e);
        }
    }

}
